#include <stx/test/test.hpp>

TEST_CASE (core_fs)
{
    CHECK(fs::isBlockFile("/dev/loop0"_fp));
    CHECK(fs::isOther("/dev/loop0"_fp));
    CHECK(!fs::isBlockFile("/bin/bash"_fp));

    CHECK(fs::isCharacterFile("/dev/tty"_fp));
    CHECK(fs::isOther("/dev/tty"_fp));
    CHECK(!fs::isCharacterFile("/bin/bash"_fp));

    CHECK(fs::isDirectory("/tmp"_fp));
    CHECK(!fs::isOther("/tmp"_fp));
    CHECK(!fs::isDirectory("/bin/bash"_fp));

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        fs::touch(tmp_path);
        CHECK(fs::exists(tmp_path));
        CHECK(!fs::isOther(tmp_path));
        CHECK(fs::isRegularFile(tmp_path));
        CHECK(fs::isEmpty(tmp_path));
        CHECK(!fs::isEmpty("/bin/bash"_fp));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_path));

        fs::createDirectory(tmp_path);
        CHECK(fs::exists(tmp_path));
        CHECK(!fs::isOther(tmp_path));
        CHECK(fs::isDirectory(tmp_path));
        CHECK(fs::isEmpty(tmp_path));
        CHECK(!fs::isEmpty("/bin"_fp));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_path));

        CHECK(0 == ::mkfifo(tmp_path.c_str(), S_IRWXU));
        CHECK(fs::isFifo(tmp_path));
    }

    {
        auto const skt = ::socket(AF_UNIX, SOCK_STREAM, 0);
        ON_SCOPE_EXIT(::close(skt));
        CHECK(fs::isSocket("/proc"_fp / tos(::getpid()) / "fd"_fp / tos(skt)));
    }

    {
        auto const skt = ::socket(AF_INET, SOCK_STREAM, 0);
        ON_SCOPE_EXIT(::close(skt));
        CHECK(fs::isSocket("/proc"_fp / tos(::getpid()) / "fd"_fp / tos(skt)));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        fs::createSymlink("/bin/bash"_fp, tmp_path);
        CHECK(fs::exists(tmp_path));
        CHECK(fs::isSymlink(tmp_path));
        CHECK("/bin/bash"_fp == fs::readSymlink(tmp_path));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        fs::createSymlink("/bin/bash"_fp, tmp_path);
        CHECK(fs::exists(tmp_path));
        CHECK(fs::isSymlink(tmp_path));
        CHECK(fs::isRegularFile(tmp_path));
        CHECK("/bin/bash"_fp == fs::readSymlink(tmp_path));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        fs::createDirectorySymlink("/usr/include"_fp, tmp_path);
        CHECK(fs::exists(tmp_path));
        CHECK(fs::isSymlink(tmp_path));
        CHECK(fs::isDirectory(tmp_path));
        CHECK("/usr/include"_fp == fs::readSymlink(tmp_path));
    }

    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        {
            ON_SCOPE_EXIT(fs::remove(tmp_path));

            fs::touch(tmp_path);
            CHECK(fs::exists(tmp_path));

            {
                auto const tmp_hard_link_1 = "/tmp"_fp / genUuidString();
                ON_SCOPE_EXIT(fs::remove(tmp_hard_link_1));
                CHECK(fs::getHardLinkCount(tmp_path) == 1);
                fs::createHardLink(tmp_path, tmp_hard_link_1);
                CHECK(fs::getHardLinkCount(tmp_path) == 2);
            }

            CHECK(fs::getHardLinkCount(tmp_path) == 1);
        }

        CHECK(!fs::exists(tmp_path));
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        auto const tmp_sub_dir    = tmp_parent_dir / genUuidString();

        {
            fs::createDirectories(tmp_sub_dir);
            ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
            CHECK(fs::getHardLinkCount(tmp_sub_dir) == 2);
            CHECK(fs::getHardLinkCount(tmp_parent_dir) == 3);
        }

        CHECK(!fs::exists(tmp_sub_dir));
        CHECK(!fs::exists(tmp_parent_dir));
    }

    {
        auto const disk_space_info = fs::space("/tmp"_fp);
        CHECK(!isMax(disk_space_info.available));
        CHECK(!isMax(disk_space_info.free));
        CHECK(!isMax(disk_space_info.capacity));
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^.*orl.*$)">();
        auto        match_cnt =
            ranges::distance(fs::getEntryRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt = fs::removeEntriesRecursive(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt =
            ranges::distance(fs::getEntryRangeRecursive(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^.*[0-9].*$)">();
        auto match_cnt = ranges::distance(fs::getFileRange(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt = fs::removeFiles(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt = ranges::distance(fs::getFileRange(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);

        match_cnt =
            ranges::distance(fs::getFileRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^.*[0-9].*$)">();
        auto        match_cnt =
            ranges::distance(fs::getFileRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt = fs::removeFilesRecursive(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt =
            ranges::distance(fs::getFileRangeRecursive(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^.*[0-9].*$)">();
        auto match_cnt = ranges::distance(fs::getFileRange(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt = fs::removeFiles(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt = ranges::distance(fs::getFileRange(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);

        match_cnt =
            ranges::distance(fs::getFileRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^.*[0-9].*$)">();
        auto        match_cnt =
            ranges::distance(fs::getDirRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt =
            fs::removeDirectoriesRecursive(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt =
            ranges::distance(fs::getDirRangeRecursive(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);
    }

    {
        auto const tmp_parent_dir = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::removeAll(tmp_parent_dir));
        auto const tmp_sub_dir = tmp_parent_dir / genUuidString();

        LOOP_N (rand(10, 20))
        {
            auto sub_dir1 = tmp_parent_dir / genUuidString();
            auto sub_dir2 = tmp_parent_dir / "hello"_fp;
            fs::createDirectories(sub_dir1);
            fs::createDirectories(sub_dir2);

            LOOP_N (rand(10, 20))
            {
                fs::touch(tmp_parent_dir / genUuidString());
                fs::touch(sub_dir1 / genUuidString());
                fs::touch(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 / genUuidString());
                fs::createDirectories(sub_dir2 /
                                      scat("world"sv, genUuidString()));
            }
        }

        auto const& re = rgx<R"(^/tmp/[0-9a-fA-F]+/.*[0-9].*$)">();
        auto match_cnt = ranges::distance(fs::getDirRange(tmp_parent_dir, re));
        CHECK(match_cnt > 0);

        auto const removed_cnt = fs::removeDirectories(tmp_parent_dir, re);
        CHECK(removed_cnt > 0);

        match_cnt = ranges::distance(fs::getDirRange(tmp_parent_dir, re));
        CHECK(0 == match_cnt, match_cnt);

        match_cnt =
            ranges::distance(fs::getDirRangeRecursive(tmp_parent_dir, re));
        CHECK(match_cnt > 0);
    }

    auto const valid_perm_numbers = []
    {
        auto ret = std::vector<std::size_t>{};
        ret.reserve(8 * 8 * 8);
        LOOP (i, 8)
        {
            LOOP (j, 8)
            {
                LOOP (k, 8)
                {
                    insert(ret, i * 100 + j * 10 + k);
                }
            }
        }

        return ret;
    }();

    for (auto const n : valid_perm_numbers)
    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        CHECK(!fs::exists(tmp_path));
        fs::touch(tmp_path);
        CHECK(fs::exists(tmp_path));
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        auto is_caught = false;

        try
        {
            fs::setPermissions(tmp_path, fs::Permissions(n));
            auto const s_perms =
                tos(std::to_underlying(fs::getFilePerms(tmp_path)),
                    NumberBase::K_8);
            CHECK(tos(n) == s_perms);
        }
        catch (...)
        {
            is_caught = true;
        }

        CHECK(!is_caught, n);
    }

    LOOP_N (100)
    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        CHECK(!fs::exists(tmp_path));
        fs::touch(tmp_path);
        CHECK(fs::exists(tmp_path));
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        auto const n         = rand(-1000, 1000);
        auto       is_caught = false;

        try
        {
            fs::setPermissions(tmp_path, fs::Permissions(n));
            auto const s_perms =
                tos(std::to_underlying(fs::getFilePerms(tmp_path)),
                    NumberBase::K_8);
            CHECK(tos(n) == s_perms);
        }
        catch (...)
        {
            is_caught = true;
        }

        if (ranges::binary_search(valid_perm_numbers, n))
        {
            CHECK(!is_caught, n);
        }
        else
        {
            CHECK(is_caught, n);
        }
    }

    LOOP_N (10)
    {
        auto const tmp_path = "/tmp"_fp / genUuidString();
        CHECK(!fs::exists(tmp_path));
        auto s_body_1 = genUuidString();
        writeTo(tmp_path, s_body_1).get();
        CHECK(fs::exists(tmp_path));
        ON_SCOPE_EXIT(fs::remove(tmp_path));

        auto s_body_2 = readFileToString(tmp_path).get();
        CHECK(s_body_2.size() == 32);
        CHECK(s_body_1 == s_body_2);
    }
}